package com.z.vote;

import cn.hutool.core.lang.UUID;
import cn.hutool.cron.CronUtil;
import cn.hutool.cron.task.Task;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
public class VoteCrawl {
    final static List<String> IP_LIST = Lists.newArrayList("180.104.72.44",
            "183.92.36.191",
            "221.236.170.167",
            "182.38.124.97",
            "106.125.161.48",
            "220.186.158.37",
            "117.28.67.90",
            "183.166.248.71",
            "115.153.143.242");
    /**
     * 每个设备投票限制数
     */
    private static final int VOTE_LIMIT = 10;
    /**
     * 投票最大并发数
     */
    private static final int MAX_OCCURS = 50;
    /**
     * 线程队列大小
     */
    private static final int QUEUE_CAPACITY = 5000;

    /**
     * 投票最大并发数
     */
    private static int CURRENT_COUNT = 0;

    /**
     * 投票最大并发数
     */
    private static int SECOND_VOTE_NUM = 0;

    /**
     * 投票休息间隔
     */
    private static Boolean SLEEP = Boolean.FALSE;
    /**
     * 投票开关
     */
    private static Boolean STOP = Boolean.FALSE;

    /**
     * 安全距离
     */
    private static final int SAFE_DISTANCE = 2000;


    private static ExecutorService EXECUTORS = new ThreadPoolExecutor(MAX_OCCURS, MAX_OCCURS, 30000, TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(QUEUE_CAPACITY), new ThreadFactoryBuilder().setNameFormat("zvote-executor").build(),
            new ThreadPoolExecutor.CallerRunsPolicy());

    public static void main(String[] args) throws Exception {
        work();
    }

    public static void work() {
//        tasks();
        while (true) {
            if (STOP) {
                try {
                    Thread.sleep(2000L);
                    log.info("now  we  are first , so no  need to vote  thanks");
                    continue;
                } catch (Exception e) {
                    log.error("sleep error", e);
                }
            }
            EXECUTORS.execute(() -> {
                String device = UUID.randomUUID().toString();
                try {
                    vote(device);
                    log.info("vote  device={} finished", device);
                } catch (Exception e) {
                    log.error("vote device={} error", device, e);
                }
            });
        }
    }

    public static void tasks() {
        //首次心跳探测
        heartBeatDetect();
        //排行探测
        rankDetect();
        //心跳探测任务
        CronUtil.schedule("0 */1 * * * *", (Task) () -> {
            heartBeatDetect();
        });
        //心跳探测任务
        CronUtil.schedule("*/10 * * * * *", (Task) () -> {
            rankDetect();
        });
        CronUtil.setMatchSecond(true);
        CronUtil.start();
    }

    public static void vote(String device) {
        for (int count = 0; count < VOTE_LIMIT; count++) {
            if (STOP) {
                break;
            }
            crawl(device);
        }
    }

    public static void heartBeatDetect() {
        String device = UUID.randomUUID().toString();
        crawl(device);
    }

    public static Boolean crawl(String device) {
        try {
            Map<String, Object> map = Maps.newHashMap();
            map.put("vid", 16);
            map.put("option", 3882);
            map.put("uid", 0);
            map.put("device", device);
            map.put("token", "");

            Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("220.186.158.37", 57114));
            String json = HttpRequest.post("http://www.cptv2018.com/jhxt/api.php?s=/Vote/setCzmVote")
                    .header(Header.USER_AGENT, "Mozilla/5.0 (iPhone; CPU iPhone OS 13_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 bjcpapp/1.5.3")
                    .header(Header.ACCEPT, "application/json, text/javascript, */*; q=0.01")
                    .header(Header.HOST, "www.cptv2018.com")
                    .header("X-Requested-With", "XMLHttpRequest")
                    .header(Header.ACCEPT_LANGUAGE, "zh-cn")
                    .header(Header.ACCEPT_ENCODING, "gzip, deflate")
                    .header(Header.CONTENT_TYPE, "application/x-www-form-urlencoded; charset=UTF-8")
                    .header(Header.ORIGIN, "http://www.cptv2018.com")
                    .header(Header.REFERER, "http://www.cptv2018.com/jhxt/api.php?s=/Vote/candidator/cid/3882/uid/0/device/" + device + "/token//vid/16")
                    .timeout(20000)
                    .form(map).setProxy(proxy)
                    .execute().body();
            log.info("vote  device={} content={}", device, json);
            if (json.contains("Internal Server Error")) {
                log.info("vote  stop for Internal Server Error  device={} content={}", device, json);
//                stop();
                return Boolean.FALSE;
            }
            JSONObject jsonObject = JSON.parseObject(json);
            if (jsonObject.getString("status").equals("error")) {
                return Boolean.TRUE;
            }
            int count = jsonObject.getJSONObject("data").getIntValue("count");
            int rank = jsonObject.getJSONObject("data").getIntValue("rank");
            int diff = jsonObject.getJSONObject("data").getIntValue("diff");
            if (diff == 0 && rank == 1 ) {
                log.info("vote  stop for diff == 0  device={} content={}", device, json);
                stop();
                return Boolean.FALSE;
            } else {
                resume();
                return Boolean.TRUE;
            }
        } catch (Exception e) {
            log.error("vote  crawl error  device={}", device, e);
            STOP = Boolean.TRUE;
            return Boolean.FALSE;
        } finally {
            try {
                if (SLEEP) {
                    long millis = RandomUtils.nextLong(500, 1000);
                    Thread.sleep(millis);
                    log.info("sleep millis={}", millis);
                }
            } catch (Exception e) {
                log.error("vote  crawl sleep error  device={}", device, e);
            }
        }
    }


    public static void rankDetect() {
        try {
            String html = HttpRequest.get("http://www.cptv2018.com/jhxt/api.php?s=/Vote/rank/vid/16/uid/0/device/B9771798-2FAA-4320-A57B-873ECF9D094D/token/")
                    .header(Header.HOST, "www.cptv2018.com")
                    .header(Header.COOKIE, "PHPSESSID=tadb4anv8pnuqkn90s723unqr6; CEB3E121E97CDF02C8BB8F68120A6E1A_idvst=; CEB3E121E97CDF02C8BB8F68120A6E1Apdv28191=3; CEB3E121E97CDF02C8BB8F68120A6E1Aatxyeq3674=3ced8792-e633-459d-948f-bd5bbd38fbf5")
                    .header("Upgrade-Insecure-Requests", "1")
                    .header(Header.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
                    .header(Header.USER_AGENT, "Mozilla/5.0 (iPhone; CPU iPhone OS 13_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 bjcpapp/1.5.3")
                    .header(Header.REFERER, "http://www.cptv2018.com/jhxt/api.php?s=/Vote/vote/vid/16?s=/device/B9771798-2FAA-4320-A57B-873ECF9D094D/version/1.5.3/timestamp/1601022529.988159/nonce/1801979802/code/223cc60282aca809df02bfe7917685c6d7a4a24a")
                    .header("X-Requested-With", "XMLHttpRequest")
                    .header(Header.ACCEPT_LANGUAGE, "zh-cn")
                    .header(Header.ACCEPT_ENCODING, "gzip, deflate")
                    .timeout(20000)
                    .execute().body();
            Document doc = Jsoup.parse(html);
            Element element = doc.getElementById("vote_list");
            Elements elements = element.getElementsByTag("li");
            for (Element elementChild : elements) {
                Integer itemIndex = Integer.valueOf(elementChild.getElementsByClass("item-index").text());
                //和第二名相差低于1000票恢复刷票
                if (itemIndex == 2) {
                    Integer itemVoteNum = Integer.valueOf(elementChild.getElementsByClass("item-vote-num").text());
                    SECOND_VOTE_NUM = itemVoteNum;
                    Boolean hit = Boolean.FALSE;
                    if (itemVoteNum + SAFE_DISTANCE >= CURRENT_COUNT) {
                        resume();
                        hit = Boolean.TRUE;
                    }
                    log.info("rankDetect hit={} itemIndex={} itemVoteNum={} SAFE_DISTANCE={} CURRENT_COUNT={}", hit, itemIndex, itemVoteNum, SAFE_DISTANCE, CURRENT_COUNT);
                    break;
                }
            }
        } catch (Exception e) {
            log.error("vote  crawl error  device={}", e);
        }
    }

    public static void stop() {
        STOP = Boolean.FALSE;
    }

    public static void resume() {
        STOP = Boolean.FALSE;
    }

}

